use core::fmt;
use std::{ops::Deref, sync::Arc};

use sqlx::PgPool;

use crate::{
    config::AppConfig,
    errors::error::AppError,
    middlewares::TokenVerify,
    models::user::User,
    utils::jwt::{DecodingKey, EncodingKey},
};

#[derive(Debug, Clone)]
pub struct AppState {
    pub inner: Arc<AppStateInner>,
}

pub struct AppStateInner {
    pub config: AppConfig,
    pub dk: DecodingKey,
    pub ek: EncodingKey,
    pub pool: PgPool,
}

impl Deref for AppState {
    type Target = AppStateInner;
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl fmt::Debug for AppStateInner {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AppStateInner")
            .field("config", &self.config)
            .finish()
    }
}

impl TokenVerify for AppState {
    type Error = AppError;
    fn verify(&self, token: &str) -> Result<User, Self::Error> {
        let user = self.dk.verify(token)?;
        Ok(user)
    }
}

impl AppState {
    pub async fn try_new(config: AppConfig) -> Result<Self, AppError> {
        let pool = PgPool::connect(&config.server.db_url).await?;
        let dk = DecodingKey::load(&config.auth.pk)?;
        let ek = EncodingKey::load(&config.auth.sk)?;
        let inner = AppStateInner {
            config,
            dk,
            ek,
            pool,
        };
        Ok(Self {
            inner: Arc::new(inner),
        })
    }
}
